Linux 上的 UPS 工具 NUT
NUT 是 C/S 架构的软件。它的优点在于省成本,不用买很贵的带网络管理卡的 UPS,只需要一个 master 节点能和 UPS 通信就够了。断电时候 master 节点可以通过网络通知 slave 节点关机。
它的缺点是,如果停电后、来电前的时间间隔较短,UPS 没有自动关闭,那么,来电后,所有节点启动时,要先等 master 节点启动 (server 所在机器),然后再在 master 节点上使用 wake-on-lan 唤醒其他 slave 节点。而如果停电后、来电前的时间间隔较长,UPS 自动关闭,那么,来电后 UPS 重新开机也可以让所有连接到该 UPS 的主机自动开机。不必先等 master 节点启动,再由 master 节点唤醒其他节点。
UPS 为 APC BK650M2-CH,它使用 USB 与主机通信。以下操作在 Debian 12 / RHEL 9 测试通过,nut-server 和 nut-client 的版本号均为 v2.8.0。
NUT 的配置文件:
- nut.conf (影响 nut-server) 被用于配置 NUT 的工作模式
- ups.conf (影响 nut-server) 被用于配置 NUT 和 UPS 的连接。也可用于重载 NUT 某些默认的配置,比如:
battery.charge.low
表示触发自动关机时的 UPS 电量battery.runtime.low
表示出发自动关机时的设备可运行时间
- upsd.conf (影响 nut-server) 当一个 UPS 被用来保护多台机器时,这个文件被用于配置 slave 访问 master 的方式
- upsd.users (影响 nut-server) 被用于配置访问 nut-server 的角色
- upsmon.conf (影响 nut-client/nut-monitor) 被用于配置 NUT 要监视哪个 UPS,在什么时候执行关机操作
- upssched.conf 中可以配置用户自定义的操作,比如脚本之类,并配置触发条件
man [ nut.conf | ups.conf | upsd.conf | upsd.users | upsmon.conf | upssched.conf ]
可查看详细信息。
NUT 工具包在 Debian 系系统和 RedHat 系系统上的不同:
- 安装 nut-server 和 nut-client:
- 在 Debian 系系统上执行
sudo apt install nut
。Debian 系系统中的 nut 包包装了 nut-server 和 nut-client 两个包,所以直接装 nut 包即可 - 在 RedHat 系系统上的 nut 包 (对应 Debian 系系统的 nut-server 包) 和 nut-client 包只存在于 EPEL 中,但所幸这两个包的所有依赖都在 BaseOS 中,所以把这两个包的 rpm 下载到本地然后手动执行
sudo dnf intsall nut-2.8.0-3.el9.x86_64.rpm nut-client-2.8.0-3.el9.x86_64.rpm
安装并不复杂,还可以避免安装 EPEL。命令中的版本号为写本文时使用的版本
- 在 Debian 系系统上执行
- 配置文件:
- Debian 系系统中,配置文件所在的目录是 /etc/nut (后文以该路径为例)
- RedHat 系系统中,配置文件所在的目录是 /etc/ups
- 配置相关进程的自动启动:
sudo systemctl enable nut.target
让 nut-server.service, nut-monitor.service 自动启动,并且自动加载 UPS 驱动 (由 nut-driver.target 完成)。nut.target 已经完整包装了 local-fs.target, nut-driver.target, nut-server.service, nut-monitor.service 四个部分,所以不需要手动配置每个 service 和 targetsudo systemctl enable nut-driver-enumerator.path
让所有针对 /etc/nut/ups.conf 的修改及时更新 (详见后文)。缺少这一步就无法加载 UPS 相关的驱动程序,导致 nut-server 进程无法连接到 UPS
此外,网上有些教程里写用 sudo systemctl enable nut-server nut-monitor
启动 server 进程和 client 进程,然后用 sudo systemctl enable nut-driver@<ups-config-name>
生成驱动相关的 .service 文件,最后使用 systemctl 启动 nut-server.service, nut-monitor.service 和 nut-driver@<ups-config-name> 三个服务。这个做法就是前文提到的手动配置每个 service 和 target,但它不如官方提供的 nut.target 完备,而且做不到自动更新 /etc/nut/ups.conf 的信息到驱动配置中。
NUT 的 systemd 配置文件的用途:
- /usr/lib/systemd/system/nut-client.service 为 Debian 系系统独有,是指向 nut-monitor.service 的符号链接
- /usr/lib/systemd/system/nut-driver-enumerator.path 在 /etc/nut/ups.conf 发生改变后立即更新 nut-driver 的配置
- /usr/lib/systemd/system/nut-driver-enumerator.service 根据 /etc/nut/ups.conf 生成一次 nut-driver 的配置
- /usr/lib/systemd/system/nut-driver@.service 用于管理 UPS 驱动,在加载 nut-driver.target 时被调用
- /usr/lib/systemd/system/nut-driver.target 用于被 nut.target 调用,从而启动与管理 UPS 驱动相关的 .service 文件
- /usr/lib/systemd/system/nut-monitor.service 用于管理 server 进程
- /usr/lib/systemd/system/nut-server.service 用于管理 client 进程
- /usr/lib/systemd/system/nut.target 用于包装 local-fs.target, nut-driver.target, nut-server.service, nut-monitor.service 四个部分
配置目标:
- 两台主机 A 和 B 都由 UPS 供电,其中:
- A 既是 server 也是 client,通过 USB 直接连接 UPS 并接入网络:
- server 和 client 是不同的进程
- server 进程负责通信,关机操作由 client 进程完成,所以 A 也需要一个 client 进程来完成关机操作
- B 作为 client 接入网络并由 A 控制关机
- A 既是 server 也是 client,通过 USB 直接连接 UPS 并接入网络:
- 在 nut-server 所在主机 A 上屏蔽 UPS 主动发来的低电量状态信号:
- 有些 UPS 检测到市电输入消失就会立即发送低电量信号,而 NUT 检测到低电量信号就会触发关机操作
- 为了避免这个问题,直接把 UPS 发来的低电量信号屏蔽掉,由 NUT 自己判断什么时候触发低电量状态
- 在 nut-server 所在主机 A 上不使用 剩余最大可运行时间
battery.runtime.low
进行关机判定:- 这个值不很准
- UPS 剩余电量低于 60% 自动关闭 A B 主机
根据配置目标,A 主机上需要修改的文件有:
- nut.conf
- ups.conf
- upsd.conf
- upsd.users
- upsmon.conf
安装 NUT server 和 client 到 A 机器,用 RJ45 转 USB 线连接 UPS 和 A 机器,A 机器认到 UPS 的话有这样的输出:
... Bus 001 Device 002: ID 051d:0002 American Power Conversion Uninterruptible Power Supply ...
... [34032.651776] usb 1-1.4: Product: Back-UPS BK650M2-CH FW:294803G -292804G [34032.651779] usb 1-1.4: Manufacturer: American Power Conversion ...
在 A 机器上使用 sudo nut-scanner -U
扫描 UPS 设备。并将 UPS信息填入 /etc/nut/ups.conf 中:
# ...
maxretry = 3
# ...
[myups]
# 这里省略了部分内容
desc = "BK650M2-CH old"
driver = "usbhid-ups"
port = "auto"
vendorid = "051D"
productid = "0002"
product = "Back-UPS BK650M2-CH FW:294803G -292804G"
serial = "xxxxxxxxxxxx"
vendor = "American Power Conversion"
# 有了上面的 port = "auto" 配置,upsd 可以自动找到对应的 UPS
# 所以这个指定 usb 端口的选项可以注释掉
#bus = "000"
# 这个参数设置了 NUT 从 USB 接口快速更新 (quick update) UPS 状态的时间间隔
# 默认值 2s,改成 6s 可以减轻通信压力,不过 2s 的频率也没什么压力
#pollinterval = 6
# pollfreq 是从 USB 接口全量更新 (full update) UPS 状态的间隔,默认 30s
#pollfreq = 30
# ignorelb 表示让 NUT 忽略 UPS 传来的低电量状态标志,
# 有些 UPS 检测到市电输入消失就会立即发送低电量状态信号到 NUT,
# 而 NUT 检测到低电量状态信号就立即触发关机操作。
# 设置 ignorelb 忽略低电量信号之后,还需要设置 battery.charge.low
# 和 battery.runtime.low 两个变量来定义低电量状态什么时候被触发。
# -1 表示不使用这个变量。
# 变量的值满足下面条件之一就触发关机操作:
# battery.charge < battery.charge.low
# battery.runtime < battery.runtime.low
ignorelb
# 表示不使用 剩余最大可运行时间 来进行低电量状态判定
override.battery.runtime.low = -1
# 表示当 UPS 电量低于 40% 时触发低电量状态,NUT 收到低电量状态信号后关机。
# 选择 40% 是为了方便定期放电到 50% 保持电池健康,但又不让机器关机。
# 40% 的电量足够让机器在断电时有足够电量撑到所有 slave 关机后自己再关机。
override.battery.charge.low = 40
需要注意,写在 /etc/nut/ups.conf 中 UPS 驱动相关的配置由 /usr/lib/systemd/system/nut-driver-enumerator.service 处理,这个 .service 会根据 /etc/nut/ups.conf 中的信息在 /etc/systemd/system 目录下生成 nut-driver@myups.service.d, nut-driver@.service.d, nut-driver.target.wants 三个与 ups 驱动相关的目录。
这个 .service 不会被自动调用,需要用户手动调用 sudo systemctl restart nut-driver-enumerator.service
来更新驱动相关的信息。另外,由于这是个 .service 文件,所以可以将其设置为系统启动后自动执行,但并不推荐这样做。
每次更改 /etc/nut/ups.con 文件之后都需要手动执行 sudo systemctl restart nut-driver-enumerator.service
并不方便,所以,有了 /usr/lib/systemd/system/nut-driver-enumerator.path 这个路径监视服务。这个服务默认不启用,它的用途是在 /etc/nut/ups.conf 中的内容改变时,自动调用 nut-driver-enumerator.service 更新配置到驱动中。使用 sudo systemctl enable nut-driver-enumerator.path
启动路径监视服务后每次修改 /etc/nut/ups.conf 之后,配置都会被自动更新到驱动中。
A 机器 /etc/nut/nut.conf 中的配置:
# ...
MODE=netserver
# 只有一台需要使用 UPS 的设备的话,用 standalone 就行
# 但这里写 netserver 的原因是后续可能有其他的设备使用这个 UPS
# 直接写成 netserver 免得后面再改模式
A 机器 /etc/nut/upsd.conf 中的配置:
# ...
# 这个文件里的多数配置保持默认就够用。
# 如果机器有多个 IP 就读一下 29 到 45 行的 LISTEN 部分,然后自己配置允许哪些 IP 访问 nut-server
# 或者直接写成下面这样,允许从本机的所有 IP 发来的数据包访问 nut-server
LISTEN 0.0.0.0 3493
A 机器 /etc/nut/upsd.users 中的配置:
# ...
# 这个给 nut-server 所在主机上的 client
# [] 中的是 username
# upsmon 值选 slave 表示这台主机在收到关机信号以后立刻关机
# 选 master 表示这台主机最后关机 (等 slave 全部关机以后再关机)
[admin]
password = adminpwd
upsmon master
# 这是 client 要使用的角色,由于需求简单,一个角色就够用了
[slave1]
password = slave1pwd
upsmon slave
在 A 机器上执行 sudo systemctl restart nut-server
让 nut-server 重读 /etc/nut/upsd.conf 和 /etc/nut/upsd.users 并以此配置启动。
在 A 机器上执行 /bin/upsc myups
以检测 nut-server 是否工作正常,输出类似这样的:
Init SSL without certificate database battery.charge: 100 battery.charge.low: 40 battery.mfr.date: 2001/01/01 battery.runtime: 1416 battery.runtime.low: -1 battery.type: PbAc battery.voltage: 13.6 battery.voltage.nominal: 12.0 device.mfr: American Power Conversion device.model: Back-UPS BK650M2-CH device.serial: xxxxxxxxxxxx device.type: ups driver.flag.ignorelb: enabled driver.name: usbhid-ups driver.parameter.pollfreq: 30 driver.parameter.pollinterval: 2 driver.parameter.port: auto driver.parameter.synchronous: no driver.version: 2.7.4 driver.version.data: APC HID 0.96 driver.version.internal: 0.41 input.sensitivity: low input.transfer.high: 278 input.transfer.low: 160 input.voltage: 230.0 input.voltage.nominal: 220 ups.beeper.status: disabled ups.delay.shutdown: 20 ups.firmware: 294803G -292804G ups.load: 19 ups.mfr: American Power Conversion ups.mfr.date: 2022/03/02 ups.model: Back-UPS BK650M2-CH ups.productid: 0002 ups.realpower.nominal: 390 ups.serial: xxxxxxxxxxxx ups.status: OL ups.test.result: Done and passed ups.timer.reboot: 0 ups.timer.shutdown: -1 ups.vendorid: 051d
如果需要实时监测输出结果可以用 watch -n 1 /bin/upsc myups
每隔一秒输出一次命令执行结果。
A 机器 /etc/nut/upsmon.conf 中的配置:
# ...
# 这里写 NUT 要监控些什么
# 主要读一读 32 到 84 行
# 写下面的配置就够了
MONITOR myups@localhost 1 admin adminpwd master
# MONITOR 表示命令
# myups 是 ups.conf 里定义的配置名称
# @ 是连接符,localhost 是主机地址
# 1 表示这台机器只有一个电源
# ( 条件宽裕的机房里每个服务器有 2 个 UPS,有 2 个 UPS 的机器得写更多配置信息,这里略过 )
# admin adminpwd master 是 upsd.conf 里定义的账户、密码与对应角色
最后在 A 机器执行 sudo systemctl restart nut-monitor
启动 client。
至此 A 机器配置完成。
可以先在 A 机器上执行 watch -n 1 /bin/upsc myups
把 UPS 信息页面拉起来,然后拔掉 UPS 电源测试一下。如果一切正常,拔掉 UPS 电源之后,机器会继续运行,直到 UPS 剩余电量到 40% 的时候,机器关机。
实际使用时可能在连到 A 机器的 SSH 里看到 UPS 剩余电量低于 40% 的时候才关机,这是正常的:
- 因为 /etc/nut/nut.conf 中配置了
netserver
模式,触发关机指令时,nut-server 所在的 master 节点会先通知各个 slave 节点关机,然后自己再关机。这个过程中:- 如果这个时候刚好 slave 掉线了,那么 nut-server 在等待
DEADTIME
定义的秒数之后才放弃连接这个 slave - 如果 slave 全部都收到关机信号了。那么 master 还会继续等待
FINALDELAY
定义的秒数之后才对本机发出关机指令 - (更多配置在 /etc/nut/upsmon.conf 中,可自行查看)
- 如果这个时候刚好 slave 掉线了,那么 nut-server 在等待
- 如果 nut-server 所在的 master 节点跑的服务比较复杂,那么关机时停止各项服务也需要时间。比如,虚拟化服务器关机前要迁移或关闭其上的虚拟机,这个过程很花时间
根据配置目标,B 主机中需要修改的文件有:
- nut.conf
- upsmon.conf
在 B 机器上安装 NUT client 并配置自动启动:
- Debian 系
sudo apt install nut-client && sudo systemctl enable nut-monitor
- RedHat 系
sudo dnf install nut-client-2.8.0-3.el9.x86_64.rpm && sudo systemctl enable nut-monitor
这里需要注意,B 机器只作为 nut-client,所以不需要配置开机自动加载 nut.target;B 机器不直接与 UPS 通信,所以不需要配置 ups.conf,也就不必配置 nut-driver-enumerator.path。只需要把 nut-monitor 进程拉起来就行。
B 机器 /etc/nut/nut.conf 中的配置:
# ...
MODE=netclient
B 机器 /etc/nut/upsmon.conf 中的配置:
# ...
# 这里写 NUT 要监控些什么
# 主要读一读 32 到 84 行
# 写下面的配置就够了
MONITOR myups@<host-A> 1 slave1 slave1pwd slave
# <host-A> 可以是 IP, FQDN 或者主机名
# 这里的 slave1 slave1pwd slave 是在 A 机器上
# /etc/nut/upsd.users 中定义的用户配置
最后在 B 机器执行 sudo systemctl restart nut-monitor
启动 client。
至此 B 机器配置完成。